﻿#region Copyrights

/*
    - - - - - - - - - - - - - - - - - - - - - - -
    Nome do Arquivo : Filter.cs
    - - - - - - - - - - - - - - - - - - - - - - -

    Sistema				    :  	LLS.Core
    Modulo				    :  	LLS.DataModel.Core
    Autor				    :	Leonardo L. de Siqueira
    Data					:	27/02/2012
    Descrição  				:	Classe de filtro utilizados para execução dos métodos de carga e exclusão da camada de negócios e dados.
    
*/

#endregion

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;

namespace LLS.DataModel.Core
{
    #region Enumerators

    /// <summary>
    /// Enum com os tipo válidos de posição para a expressão 'Like'
    /// </summary>
    public enum PositionType
    {
        /// <summary>
        /// O valor será posicionado a esquerda
        /// </summary>
        Left,
        /// <summary>
        /// O valor será posicionado ao meio
        /// </summary>
        Middle,
        /// <summary>
        /// O valor será posicionado a direita
        /// </summary>
        Right
    }

    /// <summary>
    /// Enum com os tipo válidos de ordenação para a expressão 'OrderBy'
    /// </summary>
    public enum OrderType
    {
        /// <summary>
        /// Será feito ordenação de forma ascendente
        /// </summary>
        Ascending,
        /// <summary>
        /// Será feito ordenação de forma descendente
        /// </summary>
        Descending
    }

    #endregion

    #region Data Structures

    /// <summary>
    /// Estrutura de dados para armazenamento de dados de um parâmetro de um filtro
    /// </summary>
    public struct FilterParameter
    {
        #region Properties

        /// <summary>
        /// Resgata / define o nome do parâmetro
        /// </summary>
        public string Name { get; set; }

        /// <summary>
        /// Resgata / define o tipo do parâmetro
        /// </summary>
        public DbType Type { get; set; }

        /// <summary>
        /// Resgata / define o tamanho do parâmetro
        /// </summary>
        public int Size { get; set; }

        /// <summary>
        /// Resgata / define o valor do parâmetro
        /// </summary>
        public object Value { get; set; }

        #endregion
    }

    /// <summary>
    /// Estrutura de dados para armazenamento das ordenações usadas na cláusula 'OrderBy'
    /// </summary>
    public struct OrderByProperty
    {
        #region Properties

        /// <summary>
        /// Resgata / define o nome da propriedade
        /// </summary>
        public string Name { get; set; }

        /// <summary>
        /// Resgata / define a ordenação da propriedade
        /// </summary>
        public OrderType Order { get; set; }

        #endregion

        #region Methods

        /// <summary>
        /// Método construtor
        /// </summary>
        /// <param name="name">Nome da propriedade a ser usada na ordenação</param>
        /// <param name="order">Tipo da ordem a ser usada</param>
        public OrderByProperty(string name, OrderType order)
        {
            this = new OrderByProperty();
            this.Name = name;
            this.Order = order;
        }

        /// <summary>
        /// Método que devolve o conteúdo da ordenação em forma de string
        /// </summary>
        /// <returns>string contendo a ordenação</returns>
        public override string ToString()
        {
            return (this.Order == OrderType.Ascending) ? "ASC" : "DESC";
        }

        #endregion
    }

    #endregion

    #region Classes

    /// <summary>
    /// Classe de filtro utilizados para execução dos métodos de carga e exclusão da camada de negócios e dados
    /// </summary>
    /// <typeparam name="U">Objeto com herança do tipo DMOBase</typeparam>
    public class Filter<U>
        where U : DMOBase
    {
        #region Fields

        System.Reflection.ConstructorInfo _ci = null;
        private U _dataModel = null;
        private Type _type = typeof(U);
        private StringBuilder _expression = new StringBuilder();
        private StringBuilder _order = new StringBuilder();
        private List<FilterParameter> _parameters = new List<FilterParameter>();

        #endregion

        #region Properties

        /// <summary>
        /// Resgata os parâmetros do filtro
        /// </summary>
        public List<FilterParameter> Parameters
        {
            get { return _parameters; }
        }

        #endregion

        #region Methods

        /// <summary>
        /// Método construtor
        /// </summary>
        public Filter()
        {
            //Cria um objeto de modelo de dados tipo U
            _ci = _type.GetConstructor(System.Type.EmptyTypes);
            _dataModel = (U)_ci.Invoke(null);
        }

        /// <summary>
        /// Método para adicionar um caracter '(' ao filtro
        /// </summary>
        /// <returns>Filtro</returns>
        public Filter<U> OpenParenthesis()
        {
            _expression.Append("(");
            return this;
        }

        /// <summary>
        /// Método para adicionar um caracter ')' ao filtro
        /// </summary>
        /// <returns>Filtro</returns>
        public Filter<U> CloseParenthesis()
        {
            _expression.Append(")");
            return this;
        }

        /// <summary>
        /// Método para adicionar um operador lógico 'AND' ao filtro
        /// </summary>
        /// <returns>Filtro</returns>
        public Filter<U> And()
        {
            _expression.Append(" AND ");
            return this;
        }

        /// <summary>
        /// Método para adicionar um operador lógico 'OR' ao filtro
        /// </summary>
        /// <returns>Filtro</returns>
        public Filter<U> Or()
        {
            _expression.Append(" OR ");
            return this;
        }

        /// <summary>
        /// Método para adicionar um operador lógico 'NOT' ao filtro
        /// </summary>
        /// <returns>Filtro</returns>
        public Filter<U> Not()
        {
            _expression.Append(" NOT ");
            return this;
        }

        /// <summary>
        /// Método para adicionar uma operação de comparação '=' ao filtro
        /// </summary>
        /// <param name="propertyName">Nome da propriedade do modelo de dados</param>
        /// <param name="value">Valor para usar na expressão</param>
        /// <returns>Filtro</returns>
        public Filter<U> EqualTo(string propertyName, object value)
        {
            FilterParameter parameter = new FilterParameter();
            parameter.Name = string.Concat("@PARAMETER", _parameters.Count + 1);
            parameter.Type = _dataModel.GetColumnType(propertyName);
            parameter.Size = _dataModel.GetColumnSize(propertyName);
            parameter.Value = value;
            _parameters.Add(parameter);
            _expression.Append(" {0}");
            _expression.Append(_dataModel.GetColumnName(propertyName));
            _expression.Append(" = ");
            _expression.Append(parameter.Name);
            _expression.Append(" ");
            return this;
        }

        /// <summary>
        /// Método para adicionar uma operação de comparação '&quot;' ao filtro
        /// </summary>
        /// <param name="propertyName">Nome da propriedade do modelo de dados</param>
        /// <param name="value">Valor para usar na expressão</param>
        /// <returns>Filtro</returns>
        public Filter<U> GreaterThan(string propertyName, object value)
        {
            if (string.IsNullOrEmpty(propertyName)) throw new ArgumentNullException("propertyName");

            FilterParameter parameter = new FilterParameter();
            parameter.Name = string.Concat("@PARAMETER", _parameters.Count + 1);
            parameter.Type = _dataModel.GetColumnType(propertyName);
            parameter.Size = _dataModel.GetColumnSize(propertyName);
            parameter.Value = value;
            _parameters.Add(parameter);
            _expression.Append(" {0}");
            _expression.Append(_dataModel.GetColumnName(propertyName));
            _expression.Append(" > ");
            _expression.Append(parameter.Name);
            _expression.Append(" ");
            return this;
        }

        /// <summary>
        /// Método para adicionar uma operação de comparação '&quot;=' ao filtro
        /// </summary>
        /// <param name="propertyName">Nome da propriedade do modelo de dados</param>
        /// <param name="value">Valor para usar na expressão</param>
        /// <returns>Filtro</returns>
        public Filter<U> GreaterThanOrEqualTo(string propertyName, object value)
        {
            if (string.IsNullOrEmpty(propertyName)) throw new ArgumentNullException("propertyName");

            FilterParameter parameter = new FilterParameter();
            parameter.Name = string.Concat("@PARAMETER", _parameters.Count + 1);
            parameter.Type = _dataModel.GetColumnType(propertyName);
            parameter.Size = _dataModel.GetColumnSize(propertyName);
            parameter.Value = value;
            _parameters.Add(parameter);
            _expression.Append(" {0}");
            _expression.Append(_dataModel.GetColumnName(propertyName));
            _expression.Append(" >= ");
            _expression.Append(parameter.Name);
            _expression.Append(" ");
            return this;
        }

        /// <summary>
        /// Método para adicionar uma operação de comparação '&gt;' ao filtro
        /// </summary>
        /// <param name="propertyName">Nome da propriedade do modelo de dados</param>
        /// <param name="value">Valor para usar na expressão</param>
        /// <returns>Filtro</returns>
        public Filter<U> LessThan(string propertyName, object value)
        {
            if (string.IsNullOrEmpty(propertyName)) throw new ArgumentNullException("propertyName");

            FilterParameter parameter = new FilterParameter();
            parameter.Name = string.Concat("@PARAMETER", _parameters.Count + 1);
            parameter.Type = _dataModel.GetColumnType(propertyName);
            parameter.Size = _dataModel.GetColumnSize(propertyName);
            parameter.Value = value;
            _parameters.Add(parameter);
            _expression.Append(" {0}");
            _expression.Append(_dataModel.GetColumnName(propertyName));
            _expression.Append(" < ");
            _expression.Append(parameter.Name);
            _expression.Append(" ");
            return this;
        }

        /// <summary>
        /// Método para adicionar uma operação de comparação '&gt;=' ao filtro
        /// </summary>
        /// <param name="propertyName">Nome da propriedade do modelo de dados</param>
        /// <param name="value">Valor para usar na expressão</param>
        /// <returns>Filtro</returns>
        public Filter<U> LessThanOrEqualTo(string propertyName, object value)
        {
            if (string.IsNullOrEmpty(propertyName)) throw new ArgumentNullException("propertyName");

            FilterParameter parameter = new FilterParameter();
            parameter.Name = string.Concat("@PARAMETER", _parameters.Count + 1);
            parameter.Type = _dataModel.GetColumnType(propertyName);
            parameter.Size = _dataModel.GetColumnSize(propertyName);
            parameter.Value = value;
            _parameters.Add(parameter);
            _expression.Append(" {0}");
            _expression.Append(_dataModel.GetColumnName(propertyName));
            _expression.Append(" <= ");
            _expression.Append(parameter.Name);
            _expression.Append(" ");
            return this;
        }

        /// <summary>
        /// Método para adicionar uma operação de comparação '&gt;&quot;' ao filtro
        /// </summary>
        /// <param name="propertyName">Nome da propriedade do modelo de dados</param>
        /// <param name="value">Valor para usar na expressão</param>
        /// <returns>Filtro</returns>
        public Filter<U> OtherThan(string propertyName, object value)
        {
            if (string.IsNullOrEmpty(propertyName)) throw new ArgumentNullException("propertyName");

            FilterParameter parameter = new FilterParameter();
            parameter.Name = string.Concat("@PARAMETER", _parameters.Count + 1);
            parameter.Type = _dataModel.GetColumnType(propertyName);
            parameter.Size = _dataModel.GetColumnSize(propertyName);
            parameter.Value = value;
            _parameters.Add(parameter);
            _expression.Append(" {0}");
            _expression.Append(_dataModel.GetColumnName(propertyName));
            _expression.Append(" <> ");
            _expression.Append(parameter.Name);
            _expression.Append(" ");
            return this;
        }

        /// <summary>
        /// Método para adicionar uma operação de comparação 'LIKE' ao filtro
        /// </summary>
        /// <param name="propertyName">Nome da propriedade do modelo de dados</param>
        /// <param name="type">Posição de localização do valor</param>
        /// <param name="value">Valor para usar na expressão</param>
        /// <returns>Filtro</returns>
        public Filter<U> Like(string propertyName, PositionType type, string value)
        {
            if (string.IsNullOrEmpty(propertyName)) throw new ArgumentNullException("propertyName");

            FilterParameter parameter = new FilterParameter();
            parameter.Name = string.Concat("@PARAMETER", _parameters.Count + 1);
            parameter.Type = _dataModel.GetColumnType(propertyName);
            parameter.Size = _dataModel.GetColumnSize(propertyName);

            if (type == PositionType.Left)
                parameter.Value = string.Concat(value, "%");
            else if (type == PositionType.Middle)
                parameter.Value = string.Concat("%", value, "%");
            else if (type == PositionType.Right)
                parameter.Value = string.Concat("%", value);

            _parameters.Add(parameter);
            _expression.Append(" {0}");
            _expression.Append(_dataModel.GetColumnName(propertyName));
            _expression.Append(" LIKE ");
            _expression.Append(parameter.Name);
            _expression.Append(" ");
            return this;
        }

        /// <summary>
        /// Método para adicionar ordenação ao filtro atual
        /// </summary>
        /// <param name="orderByProperties">Nomes das propriedades usadas na ordenação</param>
        /// <returns>Filtro</returns>
        public Filter<U> OrderBy(params OrderByProperty[] orderByProperties)
        {
            if (orderByProperties.Length == 0) throw new ArgumentNullException("orderByProperties");

            for (int i = 0; i < orderByProperties.Length; i++)
            {
                if (i > 0)
                    _order.Append(",");

                string orderType = (orderByProperties[i].Order == OrderType.Ascending) ? "ASC" : "DESC";

                _order.Append(" {0}");
                _order.Append(_dataModel.GetColumnName(orderByProperties[i].Name));
                _order.Append(" ");
                _order.Append(orderByProperties[i].ToString());

                if (i < (orderByProperties.Length - 1))
                    _order.Append(" ");
            }

            return this;
        }

        /// <summary>
        /// Método para adicionar uma operação de comparação 'BETWEEN' ao filtro
        /// </summary>
        /// <param name="propertyName">Nome da propriedade do modelo de dados</param>
        /// <param name="initialValue">Valor inicial para usar na expressão</param>
        /// <param name="finalValue">Valor final para usar na expressão</param>
        /// <returns>Filtro</returns>
        public Filter<U> Between(string propertyName, object initialValue, object finalValue)
        {
            if (string.IsNullOrEmpty(propertyName)) throw new ArgumentNullException("propertyName");

            FilterParameter initialParameter = new FilterParameter();
            FilterParameter finalParameter = new FilterParameter();
            initialParameter.Name = string.Concat("@PARAMETER", _parameters.Count + 1);
            initialParameter.Type = _dataModel.GetColumnType(propertyName);
            initialParameter.Size = _dataModel.GetColumnSize(propertyName);
            initialParameter.Value = initialValue;
            _parameters.Add(initialParameter);
            finalParameter.Name = string.Concat("@PARAMETER", _parameters.Count + 1);
            finalParameter.Type = _dataModel.GetColumnType(propertyName);
            finalParameter.Size = _dataModel.GetColumnSize(propertyName);
            finalParameter.Value = finalValue;
            _parameters.Add(finalParameter);
            _expression.Append(" {0}");
            _expression.Append(_dataModel.GetColumnName(propertyName));
            _expression.Append(" BETWEEN ");
            _expression.Append(initialParameter.Name);
            _expression.Append(" AND ");
            _expression.Append(finalParameter.Name);
            _expression.Append(" ");
            return this;
        }

        /// <summary>
        /// Retorna a expressão contida no filtro
        /// </summary>
        /// <returns>expressão contida no filtro em formato de string</returns>
        public override string ToString()
        {
            if (!string.IsNullOrEmpty(_expression.ToString()))
            {
                if (string.IsNullOrEmpty(_order.ToString()))
                    return string.Concat("WHERE", string.Format(_expression.ToString(), string.Empty));
                else
                    return string.Concat("WHERE", string.Format(_expression.ToString(), string.Empty), "ORDER BY", string.Format(_order.ToString(), string.Empty));
            }
            else if (!string.IsNullOrEmpty(_order.ToString()))
                return string.Concat("ORDER BY", string.Format(_order.ToString(), string.Empty));
            else
                return string.Empty;
        }

        /// <summary>
        /// Retorna a expressão contida no filtro
        /// </summary>
        /// <param name="alias">Alias da tabela</param>
        /// <returns>expressão contida no filtro em formato de string</returns>
        public string ToString(string alias)
        {
            alias = alias.Replace(".", string.Empty);

            if (!string.IsNullOrEmpty(_expression.ToString()))
            {
                if (string.IsNullOrEmpty(_order.ToString()))
                    return string.Concat("WHERE", string.Format(_expression.ToString(), string.Concat(alias, ".")));
                else
                    return string.Concat("WHERE", string.Format(_expression.ToString(), string.Concat(alias, ".")), "ORDER BY", string.Format(_order.ToString(), string.Concat(alias, ".")));
            }
            else if (!string.IsNullOrEmpty(_order.ToString()))
                return string.Concat("ORDER BY", string.Format(_order.ToString(), string.Concat(alias, ".")));
            else
                return string.Empty;
        }

        #endregion
    }

    #endregion
}
